/*
 * Copyright (c) 2023, Alibaba Group Holding Limited;
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#pragma once
#include "varint.hpp"

#include <climits>
#include <cstdint>
#include <limits>
#include <type_traits>
#include <variant>
namespace struct_pack::detail {
enum class type_id {
    // compatible template type
    compatible_t = 0,
    // fundamental integral type
    int32_t = 1,
    uint32_t,
    int64_t,
    uint64_t,
    int8_t,
    uint8_t,
    int16_t,
    uint16_t,
    int128_t, // Tips: We only support 128-bit integer on gcc clang
    uint128_t,
    bool_t,
    char_8_t,
    char_16_t,
    char_32_t,
    w_char_t, // Note: this type is not portable!Enable it with marco
              // STRUCT_PACK_ENABLE_UNPORTABLE_TYPE
    // fundamental float type
    float16_t, // TODO: wait for C++23 standard float type
    float32_t,
    float64_t,
    float128_t,
    v_int32_t,  // variable size int
    v_int64_t,  // variable size int
    v_uint32_t, // variable size unsigned int
    v_uint64_t, // variable size unsigned int
    // template type
    string_t = 128,
    array_t,
    map_container_t,
    set_container_t,
    container_t,
    optional_t,
    variant_t,
    expected_t,
    bitset_t,
    polymorphic_unique_ptr_t,
    // monostate, or void
    monostate_t = 250,
    // circle_flag
    circle_flag = 251,
    // end helper with user defined type ID
    type_end_flag_with_id = 252,
    // class type
    struct_t = 253,
    // end helper
    type_end_flag = 255,
};

template <typename T>
constexpr type_id get_varint_type() {
    if constexpr (std::is_same_v<var_int32_t, T>) {
        return type_id::v_int32_t;
    } else if constexpr (std::is_same_v<var_int64_t, T>) {
        return type_id::v_int64_t;
    } else if constexpr (std::is_same_v<var_uint32_t, T>) {
        return type_id::v_uint32_t;
    } else if constexpr (std::is_same_v<var_uint64_t, T>) {
        return type_id::v_uint64_t;
    } else {
        static_assert(!std::is_same_v<wchar_t, T>, "unsupported varint type!");
    }
}

template <typename T>
constexpr type_id get_integral_type() {
    if constexpr (std::is_same_v<int32_t, T>) {
        return type_id::int32_t;
    } else if constexpr (std::is_same_v<uint32_t, T>) {
        return type_id::uint32_t;
    } else if constexpr (
        std::is_same_v<
            int64_t,
            T> || (sizeof(long long) == 8 && std::is_same_v<T, long long>) ) {
        return type_id::int64_t;
    } else if constexpr (
        std::is_same_v<
            uint64_t,
            T> || (sizeof(unsigned long long) == 8 && std::is_same_v<T, unsigned long long>) ) {
        return type_id::uint64_t;
    } else if constexpr (std::is_same_v<int8_t,
                                        T> || std::is_same_v<signed char, T>) {
        return type_id::int8_t;
    } else if constexpr (std::is_same_v<
                             uint8_t,
                             T> || std::is_same_v<unsigned char, T>) {
        return type_id::uint8_t;
    } else if constexpr (std::is_same_v<int16_t, T>) {
        return type_id::int16_t;
    } else if constexpr (std::is_same_v<uint16_t, T>) {
        return type_id::uint16_t;
    }
    // In struct_pack, the char will be saved as unsigned!
    else if constexpr (std::is_same_v<char, T>
#ifdef __cpp_lib_char8_t
                       || std::is_same_v<char8_t, T>
#endif
    ) {
        return type_id::char_8_t;
    }
#ifdef STRUCT_PACK_ENABLE_UNPORTABLE_TYPE
    else if constexpr (std::is_same_v<wchar_t, T>) {
        return type_id::w_char_t;
    }
#endif
    // char16_t's size maybe bigger than 16 bits, which is not supported.
    else if constexpr (std::is_same_v<char16_t, T>) {
        static_assert(sizeof(char16_t) == 2,
                      "sizeof(char16_t)!=2, which is not supported.");
        return type_id::char_16_t;
    }
    // char32_t's size maybe bigger than 32bits, which is not supported.
    else if constexpr (std::is_same_v<char32_t, T> && sizeof(char32_t) == 4) {
        static_assert(sizeof(char32_t) == 4,
                      "sizeof(char32_t)!=4, which is not supported.");
        return type_id::char_32_t;
    } else if constexpr (std::is_same_v<bool, T> && sizeof(bool)) {
        static_assert(sizeof(bool) == 1,
                      "sizeof(bool)!=1, which is not supported.");
        return type_id::bool_t;
    }
#if __GNUC__ || __clang__
    //-std=gnu++20
    else if constexpr (std::is_same_v<__int128, T>) {
        return type_id::int128_t;
    } else if constexpr (std::is_same_v<unsigned __int128, T>) {
        return type_id::uint128_t;
    }
#endif
    else {
        /*
         * Due to different data model,
         * the following types are not allowed on macOS
         * but work on Linux
         * For example,
         * on macOS, `typedef unsigned long long uint64_t;`
         * on Linux, `typedef unsigned long int  uint64_t;`
         *
         * - long
         * - long int
         * - signed long
         * - signed long int
         * - unsigned long
         * - unsigned long int
         *
         * We add this static_assert to give more information about not
         * supported type.
         */
        static_assert(
            !std::is_same_v<wchar_t, T>,
            "Tips: Add macro STRUCT_PACK_ENABLE_UNPORTABLE_TYPE to support "
            "wchar_t");
        static_assert(
            !std::is_same_v<long, T> && !std::is_same_v<unsigned long, T>,
            "The long types have different width in "
            "different data model. "
            "see "
            "https://en.cppreference.com/w/cpp/language/"
            "types. "
            "Please use fixed width integer types. e.g. "
            "int32_t, int64_t. "
            "see "
            "https://en.cppreference.com/w/cpp/types/"
            "integer.");
        static_assert(!sizeof(T), "not supported type");
        // This branch will always compiled error.
    }
}

template <typename T>
constexpr type_id get_floating_point_type() {
    if constexpr (std::is_same_v<float, T>) {
        if constexpr (!std::numeric_limits<float>::is_iec559
                      || sizeof(float) != 4) {
            static_assert(!sizeof(T),
                          "The float type in this machine is not standard IEEE "
                          "754 32bits "
                          "float point!");
        }
        return type_id::float32_t;
    } else if constexpr (std::is_same_v<double, T>) {
        if constexpr (!std::numeric_limits<double>::is_iec559
                      || sizeof(double) != 8) {
            static_assert(!sizeof(T),
                          "The double type in this machine is not standard "
                          "IEEE 754 64bits "
                          "float point!");
        }
        return type_id::float64_t;
    } else if constexpr (std::is_same_v<long double, T>) {
        if constexpr (sizeof(long double) != 16
                      || std::numeric_limits<long double>::is_iec559) {
            static_assert(
                !sizeof(T),
                "The long double type in this machine is not standard IEEE "
                "754 128bits "
                "float point!");
        }
        return type_id::float128_t;
    } else {
        static_assert(!sizeof(T), "not supported type");
    }
}

template <typename T>
constexpr type_id get_type_id() {
    static_assert(CHAR_BIT == 8);
    // compatible member, which should be ignored in MD5 calculated.
    if constexpr (optional<T> && is_compatible_v<T>) {
        return type_id::compatible_t;
    } else if constexpr (std::is_enum_v<T>) {
        return get_integral_type<std::underlying_type_t<T>>();
    } else if constexpr (std::is_integral_v<T>
#if __GNUC__ || __CLANG__
                         || std::is_same_v<
                             __int128,
                             T> || std::is_same_v<unsigned __int128, T>
#endif
    ) {
        return get_integral_type<T>();
    } else if constexpr (std::is_floating_point_v<T>) {
        return get_floating_point_type<T>();
    } else if constexpr (detail::varint_t<T>) {
        return get_varint_type<T>();
    } else if constexpr (
        std::is_same_v<
            T,
            std::
                monostate> || std::is_same_v<T, void> || std::is_abstract_v<T>) {
        return type_id::monostate_t;
    } else if constexpr (bitset<T>) {
        return type_id::bitset_t;
    } else if constexpr (string<T>) {
        return type_id::string_t;
    } else if constexpr (array<T> || c_array<T> || static_span<T>) {
        return type_id::array_t;
    } else if constexpr (map_container<T>) {
        return type_id::map_container_t;
    } else if constexpr (set_container<T>) {
        return type_id::set_container_t;
    } else if constexpr (container<T>) {
        return type_id::container_t;
    } else if constexpr (optional<T>) {
        return type_id::optional_t;
    } else if constexpr (unique_ptr<T>) {
        if constexpr (is_base_class<typename T::element_type>) {
            return type_id::polymorphic_unique_ptr_t;
        } else {
            return type_id::optional_t;
        }
    } else if constexpr (is_variant_v<T>) {
        static_assert(
            std::variant_size_v<T> > 1
                || (std::variant_size_v<T> == 1
                    && !std::is_same_v<std::variant_alternative_t<0, T>,
                                       std::monostate>),
            "The variant should contain's at least one type!");
        static_assert(std::variant_size_v<T> < 256,
                      "The variant is too complex!");
        return type_id::variant_t;
    } else if constexpr (expected<T>) {
        return type_id::expected_t;
    } else if constexpr (is_trivial_tuple<T> || pair<T> || std::is_class_v<T>) {
        return type_id::struct_t;
    } else {
        static_assert(!sizeof(T), "not supported type");
    }
}
} // namespace struct_pack::detail